package von.seiji.cn;

import cn.hutool.core.img.ImgUtil;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;

/**
 * @Author: Amasawa Seiji
 * @Since: 2021/2/5 10:47
 * @Description: 暂无描述
 */
public class MatUtil {

    static {
        // 解决awt报错问题
        System.setProperty("java.awt.headless", "false");
        System.out.println(System.getProperty("java.library.path"));
        // 加载动态库
        URL url = ClassLoader.getSystemResource("data/opencv_java454.dll");
        System.load(url.getPath());
    }


    private static BufferedImage toBufferedImage(Mat matrix) {
        int type = BufferedImage.TYPE_BYTE_GRAY;
        if (matrix.channels() > 1) {
            type = BufferedImage.TYPE_3BYTE_BGR;
        }
        int bufferSize = matrix.channels() * matrix.cols() * matrix.rows();
        byte[] buffer = new byte[bufferSize];
        matrix.get(0, 0, buffer);
        // 获取所有的像素点
        BufferedImage image = new BufferedImage(matrix.cols(), matrix.rows(), type);
        final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        System.arraycopy(buffer, 0, targetPixels, 0, buffer.length);

        return image;
    }

    public static BufferedImage convert2img(Mat mat) {
        int width = mat.cols();
        int height = mat.rows();
        int dims = mat.channels();
        int[] pixels = new int[width * height];
        byte[] rgbdata = new byte[width * height * dims];
        mat.get(0, 0, rgbdata);
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        int index = 0;
        int r = 0, g = 0, b = 0;
        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                if (dims == 3) {
                    index = row * width * dims + col * dims;
                    b = rgbdata[index] & 0xff;
                    g = rgbdata[index + 1] & 0xff;
                    r = rgbdata[index + 2] & 0xff;
                    pixels[row * width + col] = ((255 & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | b & 0xff;
                }
                if (dims == 1) {
                    index = row * width + col;
                    b = rgbdata[index] & 0xff;
                    pixels[row * width + col] = ((255 & 0xff) << 24) | ((b & 0xff) << 16) | ((b & 0xff) << 8) | b & 0xff;
                }
            }
        }
        setRGB(image, 0, 0, width, height, pixels);
        return image;
    }

    /**
     * A convenience method for setting ARGB pixels in an image. This tries to avoid the performance
     * penalty of BufferedImage.setRGB unmanaging the image.
     */
    public static void setRGB(BufferedImage image, int x, int y, int width, int height, int[] pixels) {
        int type = image.getType();
        if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB) {
            image.getRaster().setDataElements(x, y, width, height, pixels);
        } else {
            image.setRGB(x, y, width, height, pixels, 0, width);
        }
    }

    public static double pointInPolygon(Point target, Point... points) {
        MatOfPoint2f mat = new MatOfPoint2f(points);
        /*
         * 执行轮廓点测试。 该函数确定该点是在轮廓内部，外部还是在边缘上（或与顶点重合）。 它分别返回正（内部），负（外部）或零（边缘）值。
         * 当measureDist = false时，返回值分别为+ 1，-1和0。 否则，返回值是该点与最近的轮廓边之间的有符号距离。
         * 参见下面的函数示例输出，其中针对轮廓测试每个图像像素：
         * @param contour 输入轮廓。
         * @param pt 测试点。
         * @param measureDist如果为true，则该函数估计从点到最近的轮廓边的有符号距离。 否则，该功能仅检查该点是否在轮廓内。
         */
        return Imgproc.pointPolygonTest(mat, target, false);
    }

    public static double pointInPolygon(Point target, double left, double top, double right, double bottom) {
        Point point = new Point(left, top);
        Point point1 = new Point(right, top);
        Point point2 = new Point(right, bottom);
        Point point3 = new Point(left, bottom);
        return pointInPolygon(target, point, point1, point2, point3);
    }


    /**
     * @param target
     * @param measureDist
     * @param edgeDist    默认按照左上右下
     * @return
     */
    public static double measurePointByEdge(Point target, boolean measureDist, double... edgeDist) {
        Point point = new Point(edgeDist[0], edgeDist[1]);
        Point point1 = new Point(edgeDist[2], edgeDist[1]);
        Point point2 = new Point(edgeDist[2], edgeDist[3]);
        Point point3 = new Point(edgeDist[0], edgeDist[3]);
        return measurePointByEdge(target, measureDist, point, point1, point2, point3);
    }

    /**
     * @param target
     * @param measureDist
     * @param points
     * @return
     */
    public static double measurePointByEdge(Point target, boolean measureDist, Point... points) {
        MatOfPoint2f mat = new MatOfPoint2f(points);
        /*
         * 执行轮廓点测试。 该函数确定该点是在轮廓内部，外部还是在边缘上（或与顶点重合）。 它分别返回正（内部），负（外部）或零（边缘）值。
         * 当measureDist = false时，返回值分别为+ 1，-1和0。 否则，返回值是该点与最近的轮廓边之间的有符号距离。
         * 参见下面的函数示例输出，其中针对轮廓测试每个图像像素：
         * @param contour 输入轮廓。
         * @param pt 测试点。
         * @param measureDist如果为true，则该函数估计从点到最近的轮廓边的有符号距离。 否则，该功能仅检查该点是否在轮廓内。
         */
        return Imgproc.pointPolygonTest(mat, target, measureDist);
    }


    public static byte[] mat2Byte(Mat mat, String ext) {
        MatOfByte aByte = new MatOfByte();
        Imgcodecs.imencode(ext, mat, aByte);
        return aByte.toArray();
    }

    public static BufferedImage mat2img(Mat mat, String ext) {
        byte[] bytes = mat2Byte(mat, ext);
        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
        return ImgUtil.read(stream);
    }

    public static Mat byte2Mat(byte[] bytes) {
        MatOfByte matOfByte = new MatOfByte(bytes);
        return Imgcodecs.imdecode(matOfByte, Imgcodecs.IMREAD_UNCHANGED);
    }

    public static Mat img2Mat(BufferedImage image, String ext) {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        ImgUtil.write(image, ext, out);
        byte[] bytes = out.toByteArray();
        return byte2Mat(bytes);
    }

}
